package connectors;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;

import utils.SynapseRandomizer;
import utils.math_utils.MathUtils;
import utils.math_utils.RandStream;

import components.AbstractNeuron;
import components.Synapse;


public class PreferrentialAttachment extends Connect {
	
	private Synapse [][] weightMatrix;
	
	public PreferrentialAttachment(){
		
	}
	public PreferrentialAttachment(SynapseRandomizer eeSR, SynapseRandomizer eiSR,
			SynapseRandomizer ieSR, SynapseRandomizer iiSR) {
		super(eeSR, eiSR, ieSR, iiSR);
	}
	
	/**
	 * 
	 * @param ns
	 * @return
	 */
	public Synapse [][] addPreferentialConnections(AbstractNeuron [] ns){
		int totDegree = 0;
		int count = 0;
		ArrayList<AbstractNeuron> neurons = new ArrayList<AbstractNeuron>(Arrays.asList(ns));
		ArrayList<AbstractNeuron> targets = new ArrayList<AbstractNeuron>(Arrays.asList(ns));
		Collections.sort(neurons, new compareDegree());
		ArrayList<HashSet<AbstractNeuron>> connectedPartners = new ArrayList<HashSet<AbstractNeuron>>();
		for(AbstractNeuron n : neurons) {
			totDegree += n.getFanIn().size() + n.getFanOut().size();
			connectedPartners.add(new HashSet<AbstractNeuron>());
			for(Synapse s: n.getFanOut()) {
				connectedPartners.get(count).add((AbstractNeuron)s.getTarg());
			}
			count++;
		}
		count = 0;
		for(int i = 0; i < neurons.size(); i++) {
			Collections.sort(targets, new compareDist(neurons.get(i)));
			for(int j = 0; j < neurons.size(); j++) {
				if(i == j || connectedPartners.get(i).contains(targets.get(j))){
					continue;
				}
				AbstractNeuron src = neurons.get(i);
				AbstractNeuron targ = targets.get(j);
				double conProb = targ.getFanIn().size() + targ.getFanOut().size() +
						src.getFanIn().size() + src.getFanOut().size();
				conProb = conProb/totDegree;
				if(Math.random() <= conProb) {
					count++;
					Synapse s = new Synapse(src, targ);

					if(src.isExcitatory() && targ.isExcitatory()) {
						getEeSR().randomizeSynapse(s);
						s.setStrength(3*RandStream.nextGaussian(60, 30, 1, 200, true));							
						s.setDecay(3);
						//s.setDelay(1.5);
					} else if (src.isExcitatory() && !targ.isExcitatory()) {
						getEiSR().randomizeSynapse(s);
						s.setStrength(3*RandStream.nextGaussian(60, 30, 1, 200, true));			
						s.setDecay(3);
						//s.setDelay(0.8);
					} else if (!src.isExcitatory() && targ.isExcitatory()) {
						getIeSR().randomizeSynapse(s);
						s.setStrength(3*RandStream.nextGaussian(30, 15, 1, 50, true));
						s.setDecay(6);
						//s.setDelay(0.8);
					} else {			
						getIiSR().randomizeSynapse(s);
						s.setStrength(3*RandStream.nextGaussian(30, 15, 1, 50, true));
						s.setDecay(6);
						//s.setDelay(0.8);
					}
					double dist = MathUtils.euclideanDist(src.getCoordinates(), targ.getCoordinates());
					s.setDelay(0.8 + Math.log(dist)/2);
					src.getFanOut().add(s);
					targ.getFanIn().add(s);
				}
			}
		}
		return weightMatrix;	
	}
	
	
	private class compareDegree implements Comparator<AbstractNeuron> {

		public compareDegree(){}
		
		@Override
		public int compare(AbstractNeuron arg0, AbstractNeuron arg1) {

			int n1 = ((components.AbstractNeuron) arg0).getFanIn().size() +
					((components.AbstractNeuron) arg0).getFanOut().size();
			int n2 = ((components.AbstractNeuron) arg1).getFanIn().size() +
					((components.AbstractNeuron) arg1).getFanOut().size();
					
			if(n1 > n2) {
				return 1;
			} else if(n1 < n2) {
				return -1;
			} else {
				return 0;
			}	
		}
	}

	private class compareDist implements Comparator<AbstractNeuron> {

		private AbstractNeuron source;
		
		public compareDist(AbstractNeuron source){
			this.source = source;
		}
		
		@Override
		public int compare(AbstractNeuron arg0, AbstractNeuron arg1) {

			double n1 = MathUtils.euclideanDist(source.getCoordinates(),
					arg0.getCoordinates());
			double n2 = MathUtils.euclideanDist(source.getCoordinates(),
					arg1.getCoordinates());
					
			if(n1 > n2) {
				return -1;
			} else if(n1 < n2) {
				return 1;
			} else {
				return 0;
			}	
		}
		
	}
	
	
}
